﻿using System;
using System.Text;
using System.Collections;
using System.Reflection;

namespace mfXmlSerializer
{
    /// <summary>
    /// used 4 created a map 4 /(de-)serialzation
    /// </summary>
    public static class mfSerializationMapCreator
    {
        #region Consts

        private const string C_STR_SET_PREFIX = "set_";
        private const string C_STR_GET_PREFIX = "get_";
        private const int C_STR_PROPERTY_EXTRACT_STARTINDEX = 4;
        /// <summary>
        /// start of namespace 4 system types
        /// </summary>
        public const string C_STR_ELEMENT_SERIALIZATION_NAMESPACE = "System.";
        private const string C_STR_ELEMENT_DATETIME_SERIALIZATION = "System.DateTime";
        private const string C_STR_ELEMENT_BOOLEAN_SERIALIZATION = "System.Boolean";
        /// <summary>
        /// full name of system....ArrayList type
        /// </summary>
        public const string C_STR_ELEMENT_ARRAYLIST_SERIALIZATION = "System.Collections.ArrayList";

        #endregion

        /// <summary>
        /// creates a map object 4 specified target type
        /// </summary>
        /// <param name="targetType">type is reflected 4 creating a map</param>
        /// <returns>map object</returns>
        public static mfSerializationMap CreateMapForType(Type targetType)
        {
            if (!targetType.IsCustomClass())
            {
                throw new mfXmlException(targetType + " have to be a custom class");
            }

            //create serialization map
            var map = ExtractMapElements(targetType.Name, targetType);

            //specifiy serialization strategies
            var determineSerializationStrategyAction = new mfSerializationMapCreationIterator.delCreateSpecificElement(DetermineSerializationStrategy);

            mfSerializationMapCreationIterator.IterateThroughMap(map
                                        , determineSerializationStrategyAction
                                        , null);

            return map;
        }

        private static mfSerializationMap ExtractMapElements(string mapName, Type targetType)
        {
            var map = new mfSerializationMap(mapName, targetType);
            var methods = targetType.GetMethods();
            Hashtable properties = new Hashtable();

            //first: gather all get-Methods, to ensure, that ReturnType-Property is filled --> recursive steps
            GatherGetterMethods(map, methods, properties);

            //second: search for all set-methods and run through child-maps
            GatherSetterMethods(methods, properties);

            return map;
        }

        private static void GatherSetterMethods(MethodInfo[] methods, Hashtable properties)
        {
            foreach (var method in methods)
            {
                bool setFound = CheckOnSetter(method);

                if (setFound)
                {
                    //extract name of property
                    var propName = method.Name.Substring(C_STR_PROPERTY_EXTRACT_STARTINDEX);

                    var mapElement = properties[propName] as mfMapBaseElement;

                    if (setFound)
                    {
                        mapElement.SetMethod = method;
                    }

                    //handle recursive types
                    if ((mapElement.ElementType.IsCustomClass() || mapElement.ElementType.IsArrayList())
                        && mapElement.IsComplete)
                    {
                        if (mapElement is mfMapArrayListElement)
                        {
                            var arrayListElement = mapElement as mfMapArrayListElement;
                            mapElement.ChildMap = ExtractMapElements(propName, arrayListElement.ItemType);
                        }
                        else
                        {
                            mapElement.ChildMap = ExtractMapElements(propName, mapElement.ElementType);
                        }
                    }
                }
            }
        }

        private static void GatherGetterMethods(mfSerializationMap map, MethodInfo[] methods, Hashtable properties)
        {
            foreach (var method in methods)
            {
                bool getFound = CheckOnGetter(method);

                //identify properties
                if (getFound)
                {
                    //extract name of property
                    var propName = method.Name.Substring(C_STR_PROPERTY_EXTRACT_STARTINDEX);

                    var mapElement = HandleElement(method.ReturnType, map, properties, propName);

                    if (getFound)
                    {
                        mapElement.GetMethod = method;
                    }

                }
            }
        }

        private static mfMapBaseElement HandleElement(Type targetType, mfSerializationMap map, Hashtable properties, string propName)
        {
            mfMapBaseElement mapElement = null;
            if (!properties.Contains(propName))//insert new property
            {
                mapElement = InstantiateElement(targetType, propName, mapElement);

                properties.Add(propName, mapElement);

                map.SerializationElements.Add(mapElement);
            }
            else
            {
                mapElement = properties[propName] as mfMapBaseElement;
            }

            return mapElement;
        }

        private static mfMapBaseElement InstantiateElement(Type targetType, string propName, mfMapBaseElement mapElement)
        {
            if (targetType.IsArrayList())
            {
                //gather array list specific map element properties
                var addMethod = targetType.GetMethod("Add", new Type[] { typeof(object) });
                var countMethod = targetType.GetMethod("get_Count");
                var getItemMethod = targetType.GetMethod("get_Item");

                var mapArrayElement = new mfMapArrayListElement(propName, addMethod, countMethod, getItemMethod);

                //TODO: here start to support raw ArrayList serialization support (with mfMapSimpleElement)
                if (!targetType.IsCustomClass())
                {
                    throw new mfXmlException(mapArrayElement.ItemType + " is no custom type 4 itemtype in arraylist");
                }

                //mapElement.ChildMap = ExtractMapElements(mapArrayElement.ItemType);

                return mapArrayElement;
            }

            return new mfMapCreationElement(propName);
        }



        private static bool CheckOnSetter(MethodInfo method)
        {
            if (method.Name.IndexOf(C_STR_SET_PREFIX) == 0)
            {
                return true;
            }
            return false;
        }

        private static bool CheckOnGetter(MethodInfo method)
        {
            if (method.Name.IndexOf(C_STR_GET_PREFIX) == 0)
            {
                return true;
            }
            return false;
        }

        private static mfMapBaseElement DetermineSerializationStrategy(mfMapBaseElement mapElement, int depth, InspectParameter para)
        {
            var isSystemStar = mapElement.ElementType.FullName.IndexOf(C_STR_ELEMENT_SERIALIZATION_NAMESPACE) == 0;
            var isSystemDateTime = mapElement.ElementType.FullName.IndexOf(C_STR_ELEMENT_DATETIME_SERIALIZATION) == 0;
            var isSystemBool = mapElement.ElementType.FullName.IndexOf(C_STR_ELEMENT_BOOLEAN_SERIALIZATION) == 0;

            //don't change order of these if-cascades!!! (bad but for now it works...)

            //array-list 
            if (mapElement.ElementType.IsArrayList())
            {
                return mapElement;//already specialized im extraction-phase
            }
            //class 
            if (mapElement.ElementType.IsCustomClass())
            {
                return new mfMapClassElement(mapElement);
            }
            //important @first the specialized system types
            //Boolean 
            if (isSystemBool)
            {
                return new mfMapBooleanElement(mapElement);
            }
            //DateTime 
            if (isSystemDateTime)
            {
                return new mfMapDateTimeElement(mapElement);
            }
            //simple (default for 'System.*')
            if (isSystemStar)
            {
                return new mfMapSimpleElement(mapElement);
            }
            //enum 
            if (mapElement.ElementType.IsEnum)
            {
                return new mfMapEnumElement(mapElement);
            }
            //array 
            if (mapElement.ElementType.IsArray)
            {
                return new mfMapArrayElement(mapElement);
            }

            throw new mfXmlException("no check for type: " + mapElement.ElementType + " worked");
        }

    }
}
